import frappe
from frappe.utils import flt, cint
from erpnext.controllers.queries import get_fields
from erpnext.controllers import queries
from frappe.desk.reportview import get_filters_cond, get_match_cond


@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def custom_get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
    """
    1. 获取退货单，之前的逻辑只获取被退货源单未完全开票的退货单
    2. 剔除尚未开票的被整单退货源出库单与退货单，两者都不需开票
    """

    doctype = "Delivery Note"
    fields = get_fields(doctype, ["name", "customer", "posting_date"])

    data = frappe.db.sql(
        """
        select %(fields)s
        from `tabDelivery Note`
        where `tabDelivery Note`.`%(key)s` like %(txt)s and
            docstatus = 1
            and status not in ('Stopped', 'Closed') %(fcond)s
            and (
                per_billed < 100
                or (grand_total = 0 and per_billed < 100)                
            )
            %(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(page_len)s offset %(start)s
    """
        % {
            "fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]),
            "key": searchfield,
            "fcond": get_filters_cond(doctype, filters, []),
            "mcond": get_match_cond(doctype),
            "start": start,
            "page_len": page_len,
            "txt": "%(txt)s",
        },
        {"txt": ("%%%s%%" % txt)},
        as_dict=as_dict,
    )
    dn_list = {d.name if as_dict else d[0] for d in data}
    ignored_dn_set = get_nonbillable_dns(dn_list)
    data = [d for d in data if not (d.name if as_dict else d[0]) in ignored_dn_set]

    return data

queries.get_delivery_notes_to_be_billed = custom_get_delivery_notes_to_be_billed

def get_returned_qty_map(source_names):
    """returns a map: {so_detail: returned_qty}"""
    returned_qty_map = frappe._dict(
        frappe.get_all("Delivery Note",
            filters = {'docstatus': 1,
                'is_return': 1,
                'return_against': ('in', source_names)
            },
            fields = ["`tabDelivery Note Item`.dn_detail","`tabDelivery Note Item`.qty"],
            as_list = 1
        )
    )    
    return {k:abs(v) for k,v in returned_qty_map.items()}

def get_invoiced_qty_map(source_names):
    """returns a map: {dn_detail: invoiced_qty}"""
    invoiced_qty_map = {}

    for dn_detail, qty in frappe.get_all('Sales Invoice Item',
        filters = {'delivery_note': ('in', source_names),
            'docstatus': 1}, 
        as_list = 1,
        fields =["`tabSales Invoice Item`.dn_detail","`tabSales Invoice Item`.qty"], 
    ):
        if not invoiced_qty_map.get(dn_detail):
            invoiced_qty_map[dn_detail] = 0
        invoiced_qty_map[dn_detail] += qty

    return invoiced_qty_map

def get_nonbillable_dns(source_names):

    def get_pending_qty(item_row):
        pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
        
        returned_qty = 0
        if returned_qty_map.get(item_row.name, 0) > 0:
            returned_qty = flt(returned_qty_map.get(item_row.name, 0))
            returned_qty_map[item_row.name] -= pending_qty

        if returned_qty:
            if returned_qty >= pending_qty:
                pending_qty = 0
                returned_qty -= pending_qty
            else:
                pending_qty -= returned_qty
                returned_qty = 0

        to_make_invoice_qty_map[item_row.name] = pending_qty

        return pending_qty

    data = frappe.get_all("Delivery Note", 
        filters = {'name':('in', source_names)},
        fields = ['name as dn','is_return', 'return_against',
            '`tabDelivery Note Item`.name','`tabDelivery Note Item`.qty'])
    dn_items_map = {}
    for d in data:
        dn_items_map.setdefault(d.dn, []).append(d)

    to_make_invoice_qty_map = {}
    returned_qty_map = get_returned_qty_map(source_names)
    invoiced_qty_map = get_invoiced_qty_map(source_names)
    billable_dns = set()
    for source_name  in source_names:
        items = dn_items_map.get(source_name, [])
        dn_item_names = {row.name for row in items}
        for item in items:
            #被退货源单未开过票时，因源单状态为100%退货，无法被下推发票，退货单也不能下推退货发票
            billable = (get_pending_qty(item) > 0 if not item.get("is_return") 
                else get_pending_qty(item) < 0 and 
                not is_returned_dn_billable(item.return_against, dn_item_names )) 
            if billable:
                billable_dns.add(item.dn)
                break
    non_billable_dns = [dn for dn in source_names if dn not in billable_dns] 
    return  non_billable_dns or []

def is_returned_dn_billable(returned_dn, return_dn_item_names):
    """被退货源出库单是否包括了退货数量且可开票，
    源出库被退货前已开票，则退货单可下推退货发票"""

    invoiced_qty_map = get_invoiced_qty_map([returned_dn])
    doc = frappe.get_doc('Delivery Note', returned_dn)
    returned_items = [item for item in doc.items if item.name in return_dn_item_names]
    for item in returned_items:        
        billable = (item_row.qty - invoiced_qty_map.get(item_row.name, 0)) > 0            
        if billable:
            return True
            break

@frappe.whitelist()
def is_dn_non_billable(dn):
    return bool(get_nonbillable_dns([dn]))


"""
testing
from zelin_pp.monkey_patches.queries import *
source_names = ['MAT-DN-2023-00035','MAT-DN-2023-00036','MAT-DN-2023-00037','MAT-DN-2023-00038']
dns = get_nonbillable_dns(source_names)
"""